package org.budo.graph.view.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import javax.servlet.http.HttpServletRequest;

import org.budo.graph.Constant;
import org.budo.graph.entity.DubboProvider;
import org.budo.graph.service.BudoGraphService;
import org.budo.support.lang.counter.Counter;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.ProcessUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.budo.support.spring.util.SpringUtil;
import org.budo.time.Time;
import org.budo.time.TimePoint;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;

import com.alibaba.druid.util.Utils;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.registry.RegistryService;

/**
 * @author limingwei
 */
public class DubboProviderUtil {
    private static final Logger log = Slf4j.getLogger();

    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    private static final Map<String, DubboProvider> _dubboProviderFromApplicationContextCacheMap = new ConcurrentHashMap<String, DubboProvider>();

    private static final ThreadPoolExecutor _EXECUTOR_SERVICE = (ThreadPoolExecutor) Executors.newFixedThreadPool(6);

    protected static RegistryService getRegistryService(ApplicationContext applicationContext) {
        return SpringUtil.getBean(applicationContext, RegistryService.class);
    }

    /**
     * getDubboProviderFromApplicationContext
     */
    public static DubboProvider getDubboProviderFromApplicationContext(ApplicationContext applicationContext) {
        DubboProvider dubboProvider = new DubboProvider();

        String currentProcessName = ProcessUtil.getCurrentProcessName();
        dubboProvider.setProcessName(currentProcessName);

        String startTime = Time.when(Utils.getStartTime()).toString(YYYY_MM_DD_HH_MM_SS);
        dubboProvider.setStartTime(startTime);

        dubboProvider.setSystemTime(Time.now().toString(YYYY_MM_DD_HH_MM_SS));

        Integer druidMaxActive = DruidDataSourceUtil.druidMaxActive(applicationContext);
        dubboProvider.setDruidMaxActive(druidMaxActive);

        Integer druidActiveCount = DruidDataSourceUtil.druidActiveCount(applicationContext);
        dubboProvider.setDruidActiveCount(druidActiveCount);

        Integer redisMaxTotal = BudoGraphRedisUtil.redisMaxTotal(applicationContext);
        dubboProvider.setRedisMaxTotal(redisMaxTotal);

        Integer redisNumActive = BudoGraphRedisUtil.redisNumActive(applicationContext);
        dubboProvider.setRedisNumActive(redisNumActive);

        Runtime runtime = Runtime.getRuntime();
        dubboProvider.setFreeMemory(runtime.freeMemory());
        dubboProvider.setMaxMemory(runtime.maxMemory());
        dubboProvider.setTotalMemory(runtime.totalMemory());

        String applicationName = getApplicationName(applicationContext);
        dubboProvider.setApplicationName(applicationName);

        return dubboProvider;
    }

    private static String getApplicationName(ApplicationContext applicationContext) {
        try {
            ApplicationConfig applicationConfig = applicationContext.getBean(ApplicationConfig.class);
            return applicationConfig.getName();
        } catch (Throwable e) {
            log.error("#64 getApplicationName error, e=" + e);
            return "AppName";
        }
    }

    private static DubboProvider dubboProviderMetadataFromDubboUrl(URL url, ApplicationContext applicationContext, boolean detail) {
        try {
            DubboProvider dubboProviderFromDubboUrl = new DubboProvider();
            dubboProviderFromDubboUrl.setHost(url.getIp());
            dubboProviderFromDubboUrl.setPort(url.getPort());

            String application = url.getParameter("application");
            if (null != application) {
                dubboProviderFromDubboUrl.setApplicationName(application);
            }

            String pid = url.getParameter("pid");
            if (null != pid) {
                dubboProviderFromDubboUrl.setProcessName(pid + "@");
            }

            String timestamp = url.getParameter("timestamp");
            if (null != timestamp) {
                TimePoint timePoint = Time.when(NumberUtil.toLong(timestamp));
                dubboProviderFromDubboUrl.setStartTime(timePoint.toString(YYYY_MM_DD_HH_MM_SS));
            }

            if (detail) {
                DubboProvider dubboProviderFromApplicationContext = getDubboProviderFromApplicationContext(dubboProviderFromDubboUrl);
                merge(dubboProviderFromApplicationContext, dubboProviderFromDubboUrl);
            }

            return dubboProviderFromDubboUrl;
        } catch (Throwable e) {
            log.error("#101 dubboProviderMetadataFromDubboUrl error, e=" + e, e);
            return null;
        }
    }

    private static void merge(DubboProvider from, DubboProvider to) {
        if (null == from || null == to) {
            return;
        }

        to.setApplicationName(from.getApplicationName());
        to.setProcessName(from.getProcessName());

        to.setDruidActiveCount(from.getDruidActiveCount());
        to.setDruidMaxActive(from.getDruidMaxActive());

        to.setRedisNumActive(from.getRedisNumActive());
        to.setRedisMaxTotal(from.getRedisMaxTotal());

        to.setSystemTime(from.getSystemTime());
        to.setStartTime(from.getStartTime());

        to.setBuildTime(from.getBuildTime());
        to.setSourceCodeVersion(from.getSourceCodeVersion());

        to.setMaxMemory(from.getMaxMemory());
        to.setTotalMemory(from.getTotalMemory());
        to.setFreeMemory(from.getFreeMemory());
    }

    private static DubboProvider getDubboProviderFromApplicationContext(final DubboProvider dubboProvider) {
        final String cacheKey = dubboProvider.getHost() + ":" + dubboProvider.getPort();
        if (_EXECUTOR_SERVICE.getActiveCount() >= _EXECUTOR_SERVICE.getMaximumPoolSize()) {
            log.debug("#154 _executorService is busy, _executorService=" + _EXECUTOR_SERVICE + ", dubboProvider=" + dubboProvider //
                    + ", _dubboProviderFromApplicationContextCacheMap=" + MapUtil.toString(_dubboProviderFromApplicationContextCacheMap));
            return _dubboProviderFromApplicationContextCacheMap.get(cacheKey); // 线程数满则直接返回
        }

        _EXECUTOR_SERVICE.submit(new Runnable() { // 只要有空闲线程就去异步获取新的
            public void run() {
                try {
                    BudoGraphService budoGraphService = BudoGraphViewUtil.buildBudoGraphService(dubboProvider.getHost(), dubboProvider.getPort(), null);
                    DubboProvider dubboProvider_2 = budoGraphService.dubboProviderMetadata();
                    _dubboProviderFromApplicationContextCacheMap.put(cacheKey, dubboProvider_2);
                } catch (Throwable e) {
                    if (isInvokeTimeoutError(e)) {
                        Counter counter = Counter.of("GetDubboProviderFromApplicationContextError").increment();
                        Integer value = counter.getValue();
                        if (value < 15 || 0 == value % 50) { // 减少日志
                            log.warn("#181 getDubboProviderFromApplicationContext error, e=" + e + ", counter=" + counter);
                        } else {
                            log.debug("#183 getDubboProviderFromApplicationContext error, e=" + e + ", counter=" + counter);
                        }
                    } else {
                        log.error("#186 getDubboProviderFromApplicationContext error, e=" + e, e);
                    }
                }
            }
        });

        // 返回结果
        return _dubboProviderFromApplicationContextCacheMap.get(cacheKey);
    }

    protected static boolean isInvokeTimeoutError(Throwable e) {
        String error = "" + e;
        return error.contains("Waiting server-side response timeout") //
                || error.contains("failed to connect to server") //
                || error.contains("Invoke remote method timeout") //
                || error.contains("message can not send, because channel is closed");
    }

    public static DubboProvider getDubboProvider(RegistryService registryService, String ip, Integer port, ApplicationContext applicationContext) {
        Collection<DubboProvider> lookupDubboProviders = getDubboProviders_1(applicationContext, false, true);
        if (null == lookupDubboProviders || lookupDubboProviders.isEmpty()) {
            return null;
        }

        for (DubboProvider dubboProvider : lookupDubboProviders) {
            if (null == dubboProvider) {
                continue;
            }

            if (StringUtil.equals(dubboProvider.getHost(), ip) //
                    && NumberUtil.equals(dubboProvider.getPort(), port)) {
                return dubboProvider;
            }
        }

        return null;
    }

    public static List<DubboProvider> getDubboProviderList(ApplicationContext applicationContext, HttpServletRequest request) {
        List<DubboProvider> providerList = new ArrayList<DubboProvider>();

        if (ReflectUtil.hasClass(Constant.DUBBO_NOTIFY_LISTENER)) { // 引入了dubbo依赖
            Collection<DubboProvider> providers = DubboProviderUtil.getDubboProviders_1(applicationContext, true, true);
            ListUtil.addAll(providerList, providers);
        }

        return providerList;
    }

    public static Collection<DubboProvider> getDubboProviders_1(ApplicationContext applicationContext, boolean detail, boolean showme) {
        Map<String, DubboProvider> map = new HashMap<String, DubboProvider>();
        Collection<DubboProvider> dubboProviders = getDubboProviders_2(applicationContext, detail);

        if (dubboProviders.isEmpty() || showme) {
            DubboProvider dubboProvider = new DubboProvider();
            dubboProvider.setHost("127.0.0.1");
            dubboProvider.setPort(0);
            dubboProvider.setProcessName(ProcessUtil.getCurrentProcessName());
            dubboProvider.setStartTime(Time.when(Utils.getStartTime()).toString(YYYY_MM_DD_HH_MM_SS));

            if (detail) {
                DubboProvider dubboProviderFromApplicationContext = getDubboProviderFromApplicationContext(dubboProvider);
                merge(dubboProviderFromApplicationContext, dubboProvider);
            }

            dubboProviders.add(dubboProvider);
        }

        for (DubboProvider dubboProvider : dubboProviders) {
            if (null == dubboProvider //
                    || null == dubboProvider.getProcessName() //
                    || dubboProvider.getProcessName().isEmpty()) {
                log.debug("#189 skip dubboProvider, " + dubboProvider);
                continue;
            }

            String key = dubboProvider.getHost() + ":" + dubboProvider.getPort();
            DubboProvider oldProvider = map.get(key);
            if (null == oldProvider || null == oldProvider.getStartTime()) { // 无旧的，或旧的无时间
                map.put(key, dubboProvider); //
                continue;
            }

            if (null == dubboProvider.getStartTime()) { // 新的无时间
                log.info("#201 newProvider=" + dubboProvider);
                continue; // 用旧的
            }

            // 新的时间大
            if (dubboProvider.getStartTime().compareTo(oldProvider.getStartTime()) > 0) {
                log.debug("#205 newProvider=" + dubboProvider + ", oldProvider=" + oldProvider);
                map.put(key, dubboProvider); // 替换掉
            }
        }

        return map.values();
    }

    public static List<DubboProvider> getDubboProviders_2(ApplicationContext applicationContext, boolean detail) {
        List<DubboProvider> dubboProviders = new ArrayList<DubboProvider>();
        BudoRegistryServerSync registrySync = BudoRegistryServerSync.getBudoRegistryServerSync(applicationContext);

        if (null == registrySync) {
            log.warn("#259 registrySync=" + registrySync + ", applicationContext=" + applicationContext);
            return dubboProviders;
        }

        // ConcurrentMap<category, ConcurrentMap<servicename, Map<MD5, URL>>>
        ConcurrentMap<String, ConcurrentMap<String, Map<String, URL>>> registryCache = registrySync.getRegistryCache();
        for (Entry<String, ConcurrentMap<String, Map<String, URL>>> entry : registryCache.entrySet()) {
            for (Entry<String, Map<String, URL>> entry2 : entry.getValue().entrySet()) {
                for (Entry<String, URL> entry3 : entry2.getValue().entrySet()) {
                    URL url_3 = entry3.getValue();
                    DubboProvider dubboProvider = dubboProviderMetadataFromDubboUrl(url_3, applicationContext, detail);
                    dubboProviders.add(dubboProvider);
                }
            }
        }

        return dubboProviders;
    }
}